/**
 * Copyright &copy; 2016-2017 <a href="http://git.oschina.net/whoamien/backend_management">Backend Management</a> All rights reserved.
 */
package com.xw.framework.encryption;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.shiro.codec.Base64;

/**
 * The Class NCSAESUtil.
 *
 * @author Xi Wei
 */
public class AESUtil {
	private static byte[] iv = { 56, 55, 54, 53, 52, 51, 50, 49, 56, 55, 54, 53, 52, 51, 50, 49 };

	/**
	 * Gets the AES key spec.
	 *
	 * @param keySize the key size
	 * @return the AES key spec
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 */
	public static SecretKeySpec getAESKeySpec(int keySize) throws IOException, NoSuchAlgorithmException {
		if ((keySize != 128) && (keySize != 192) && (keySize != 256)) {
			keySize = 256;
		}

		KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
		keyGenerator.init(keySize);
		SecretKey key = keyGenerator.generateKey();
		byte[] bytes = key.getEncoded();

		SecretKeySpec spec = new SecretKeySpec(bytes, "AES");

		return spec;
	}

	/**
	 * Gets the AES key spec.
	 *
	 * @param key the key
	 * @return the AES key spec
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 * @throws UnsupportedEncodingException the unsupported encoding exception
	 */
	public static SecretKeySpec getAESKeySpec(String key)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		SecretKeySpec spec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
		return spec;
	}

	/**
	 * Gets the AES key spec from hex.
	 *
	 * @param hexString the hex string
	 * @return the AES key spec from hex
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 * @throws UnsupportedEncodingException the unsupported encoding exception
	 */
	public static SecretKeySpec getAESKeySpecFromHex(String hexString)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		SecretKeySpec spec = new SecretKeySpec(hex2byte(hexString), "AES");
		return spec;
	}

	/**
	 * Gets the AES key spec from base 64.
	 *
	 * @param base64String the base 64 string
	 * @return the AES key spec from base 64
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 */
	public static SecretKeySpec getAESKeySpecFromBase64(String base64String) throws NoSuchAlgorithmException {
		SecretKeySpec spec = new SecretKeySpec(Base64.decode(base64String), "AES");
		return spec;
	}

	/**
	 * Gets the AES key spec from byte.
	 *
	 * @param bytes the bytes
	 * @return the AES key spec from byte
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 */
	public static SecretKeySpec getAESKeySpecFromByte(byte[] bytes) throws NoSuchAlgorithmException {
		SecretKeySpec spec = new SecretKeySpec(bytes, "AES");
		return spec;
	}

	/**
	 * Encrypt for JS.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String encryptForJS(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
		cipher.init(1, secretKeySpec);
		byte[] byteEnc = cipher.doFinal(text.getBytes("UTF-8"));
		return byte2hex(byteEnc);
	}
	
	/**
	 * Encrypt for JS.
	 *
	 * @param key the key
	 * @param text the text
	 * @return the string
	 */
	public static String encryptForJS(String key, String text) {
		try {
			Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
			SecretKeySpec secretKeySpec = getAESKeySpecFromHex(key.toUpperCase());
			cipher.init(1, secretKeySpec);
			byte[] byteEnc = cipher.doFinal(text.getBytes("UTF-8"));
			return Base64.encodeToString(byteEnc);
			//return byte2hex(byteEnc);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Encrypt.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String encrypt(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(1, secretKeySpec);
		byte[] byteEnc = cipher.doFinal(text.getBytes("UTF-8"));
		return byte2hex(byteEnc);
	}
	
	/**
	 * Encrypt.
	 *
	 * @param key the key
	 * @param text the text
	 * @return the string
	 */
	public static String encrypt(String key, String text)  {
		try {
			Cipher cipher = Cipher.getInstance("AES");
			SecretKeySpec secretKeySpec = getAESKeySpecFromHex(key.toUpperCase());
			cipher.init(1, secretKeySpec);
			byte[] byteEnc = cipher.doFinal(text.getBytes("UTF-8"));
			return byte2hex(byteEnc);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Encrypt for base 64.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String encryptForBase64(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(1, secretKeySpec);
		byte[] byteEnc = cipher.doFinal(Base64.decode(text));
		return Base64.encodeToString(byteEnc);
	}

	/**
	 * Encrypt for byte.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param bytes the bytes
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String encryptForByte(SecretKeySpec secretKeySpec, byte[] bytes) throws Exception {
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(1, secretKeySpec);
		byte[] byteEnc = cipher.doFinal(bytes);
		return byte2hex(byteEnc);
	}

	/**
	 * Decrypt.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String decrypt(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(2, secretKeySpec);
		return new String(cipher.doFinal(hex2byte(text)));
	}

	/**
	 * Decrypt.
	 *
	 * @param key the key
	 * @param text the text
	 * @return the string
	 */
	public static String decrypt(String key, String text)  {
		try {
			Cipher cipher = Cipher.getInstance("AES");
			SecretKeySpec secretKeySpec = getAESKeySpecFromHex(key.toUpperCase());
			
			cipher.init(2, secretKeySpec);
			String plain = new String(cipher.doFinal(hex2byte(text)));
			System.out.println("text:"+text);
			System.out.println("plain:"+plain);
			return plain;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Decrypt for JS.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String decryptForJS(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
		cipher.init(2, secretKeySpec);
		return new String(cipher.doFinal(hex2byte(text)));
	}
	
	/**
	 * Decrypt for JS.
	 *
	 * @param key the key
	 * @param text the text
	 * @return the string
	 */
	public static String decryptForJS(String key, String text) {
		try {
			Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "SunJCE");
			SecretKeySpec secretKeySpec = getAESKeySpecFromHex(key.toUpperCase());
			cipher.init(2, secretKeySpec);
			
			String plain = new String(cipher.doFinal(ConversionUtil.hex2byte(text)));
			System.out.println("text:"+text);
			System.out.println("plain:"+plain);
			
			return plain;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Decrypt for base 64.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param text the text
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String decryptForBase64(SecretKeySpec secretKeySpec, String text) throws Exception {
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(2, secretKeySpec, new IvParameterSpec(iv));
		return new String(cipher.doFinal(Base64.decode(text)));
	}

	/**
	 * Encrypt for file.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param in the in
	 * @param out the out
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws InvalidKeyException the invalid key exception
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 * @throws NoSuchPaddingException the no such padding exception
	 */
	public static void encryptForFile(SecretKeySpec secretKeySpec, File in, File out)
			throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
		Cipher aesCipher = Cipher.getInstance("AES");

		aesCipher.init(1, secretKeySpec);

		FileInputStream is = new FileInputStream(in);
		CipherOutputStream os = new CipherOutputStream(new FileOutputStream(out), aesCipher);

		copy(is, os);

		os.close();
	}

	/**
	 * Decrypt for file.
	 *
	 * @param secretKeySpec the secret key spec
	 * @param in the in
	 * @param out the out
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws InvalidKeyException the invalid key exception
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 * @throws NoSuchPaddingException the no such padding exception
	 */
	public static void decryptForFile(SecretKeySpec secretKeySpec, File in, File out)
			throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
		Cipher aesCipher = Cipher.getInstance("AES");

		aesCipher.init(2, secretKeySpec);

		CipherInputStream is = new CipherInputStream(new FileInputStream(in), aesCipher);
		FileOutputStream os = new FileOutputStream(out);

		copy(is, os);

		is.close();
		os.close();
	}

	private static void copy(InputStream is, OutputStream os) throws IOException {
		byte[] b = new byte[1024];
		int i = 0;
		while ((i = is.read(b)) != -1) {
			
			os.write(b, 0, i);
		}
	}

	/**
	 * Test JSAES decryption.
	 *
	 * @throws Exception the exception
	 */
	public static void testJSAESDecryption() throws Exception {
		String jsKey = "e8b1d1fcc5afcd8f5b16e7e647c94362";
		String[] jsEncrypted = { "2fa604c1f86748a02dc9f66279487ef6", "2fa604c1f86748a02dc9f66279487ef6" };

		SecretKeySpec secretKeySpec = getAESKeySpecFromHex(jsKey.toUpperCase());

		for (int i = 0; i < jsEncrypted.length; ++i) {
			String cipherText = jsEncrypted[i].toUpperCase();
			System.out.print("Cipher:");
			System.out.println(cipherText);
			String plainText = decryptForJS(secretKeySpec, jsEncrypted[i]);
			System.out.print("ClearText:");
			System.out.println(plainText.trim());
		}
	}
	
	/**
	 * Test JSAES encryption.
	 *
	 * @throws Exception the exception
	 */
	public static void testJSAESEncryption() throws Exception {
		String jsKey = "1d81f44b268e28fce1607a5c1d02b3b1adde60a24a9ec9492b7a5390e21b4830";
		String content = "xiwei";

		SecretKeySpec secretKeySpec = getAESKeySpecFromHex(jsKey.toUpperCase());

		System.out.print("AES Encrypt:" + encrypt(secretKeySpec, content));
	}
	
	/**
	 * Hex 2 byte.
	 *
	 * @param hexString the hex string
	 * @return the byte[]
	 * @throws UnsupportedEncodingException the unsupported encoding exception
	 */
	public static byte[] hex2byte(String hexString) throws UnsupportedEncodingException {
		if (hexString == null) {
			return null;
		}

		byte[] b = hexString.getBytes("UTF-8");

		if (b.length % 2 != 0) {
			throw new IllegalArgumentException("Length is not odd!");
		}

		byte[] b2 = new byte[b.length / 2];

		for (int n = 0; n < b.length; n += 2) {
			String item = new String(b, n, 2);
			b2[(n / 2)] = (byte) Integer.parseInt(item, 16);
		}
		return b2;
	}
	
	/**
	 * Byte 2 hex.
	 *
	 * @param bytes the bytes
	 * @return the string
	 */
	public static String byte2hex(byte[] bytes) {
		if (bytes == null) {
			return null;
		}

		String hs = "";
		String stmp = "";
		for (int n = 0; n < bytes.length; ++n) {
			stmp = Integer.toHexString(bytes[n] & 0xFF);
			if (stmp.length() == 1) {
				hs = hs + "0" + stmp;
			} else {
				hs = hs + stmp;
			}
		}
		return hs.toUpperCase();
	}

	/**
	 * The main method.
	 *
	 * @param args the arguments
	 * @throws Exception the exception
	 */
	public static void main(String[] args) throws Exception {
		try {
			String key = "75597c3ae8365fa604721df894b2628c5a082d929d73e6672d03a856c9bcaf8e";
			String content = "The quick brown fox jumps over the lazy dog. 👻 👻";
			String encrypted = encryptForJS(key, content);
			System.out.println(encrypted);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}